define(['require', './normalize'], function (req, normalize) {
    var cssAPI = {};

    var isWindows = !!process.platform.match(/^win/);

    function compress(css) {
        if (config.optimizeCss == 'none') {
            return css;
        }

        if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
            try {
                var csso = require.nodeRequire('csso');
            }
            catch (e) {
                console.log('Compression module not installed. Use "npm install csso -g" to enable.');
                return css;
            }
            var csslen = css.length;
            try {
                if (typeof csso.minify === 'function') {
                    var minifyResult = csso.minify(css);
                    if (typeof minifyResult === 'string') { // for csso < 2.0.0
                        css = minifyResult;
                    } else if (typeof minifyResult === 'object') { // for csso >= 2.0.0
                        css = minifyResult.css;
                    }
                } else { // justDoIt() was always. minify() appeared in csso 1.4.0.
                    css = csso.justDoIt(css);
                }
            }
            catch (e) {
                console.log('Compression failed due to a CSS syntax error.');
                return css;
            }
            console.log('Compressed CSS output to ' + Math.round(css.length / csslen * 100) + '%.');
            return css;
        }
        console.log('Compression not supported outside of nodejs environments.');
        return css;
    }

    //load file code - stolen from text plugin
    function loadFile(path) {
        if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
            var fs = require.nodeRequire('fs');
            var file = fs.readFileSync(path, 'utf8');
            if (file.indexOf('\uFEFF') === 0)
                return file.substring(1);
            return file;
        }
        else {
            var file = new java.io.File(path),
                lineSeparator = java.lang.System.getProperty("line.separator"),
                input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), 'utf-8')),
                stringBuffer, line;
            try {
                stringBuffer = new java.lang.StringBuffer();
                line = input.readLine();
                if (line && line.length() && line.charAt(0) === 0xfeff)
                    line = line.substring(1);
                stringBuffer.append(line);
                while ((line = input.readLine()) !== null) {
                    stringBuffer.append(lineSeparator).append(line);
                }
                return String(stringBuffer.toString());
            }
            finally {
                input.close();
            }
        }
    }


    function saveFile(path, data) {
        if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
            var fs = require.nodeRequire('fs');
            fs.writeFileSync(path, data, 'utf8');
        }
        else {
            var content = new java.lang.String(data);
            var output = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream(path), 'utf-8'));

            try {
                output.write(content, 0, content.length());
                output.flush();
            }
            finally {
                output.close();
            }
        }
    }

    //when adding to the link buffer, paths are normalised to the baseUrl
    //when removing from the link buffer, paths are normalised to the output file path
    function escape(content) {
        return content.replace(/(["'\\])/g, '\\$1')
            .replace(/[\f]/g, "\\f")
            .replace(/[\b]/g, "\\b")
            .replace(/[\n]/g, "\\n")
            .replace(/[\t]/g, "\\t")
            .replace(/[\r]/g, "\\r");
    }

    // NB add @media query support for media imports
    var importRegEx = /@import\s*(url)?\s*(('([^']*)'|"([^"]*)")|\(('([^']*)'|"([^"]*)"|([^\)]*))\))\s*;?/g;
    var absUrlRegEx = /^([^\:\/]+:\/)?\//;

    // Write Css module definition
    var writeCSSDefinition = "define('@writecss', function() {return function writeCss(c) {var d=document,a='appendChild',i='styleSheet',s=d.createElement('style');s.type='text/css';d.getElementsByTagName('head')[0][a](s);s[i]?s[i].cssText=c:s[a](d.createTextNode(c));};});";

    var siteRoot;

    var baseParts = req.toUrl('base_url').split('/');
    baseParts[baseParts.length - 1] = '';
    var baseUrl = baseParts.join('/');

    var curModule = 0;
    var config;

    var writeCSSForLayer = true;
    var layerBuffer = [];
    var cssBuffer = {};

    cssAPI.load = function (name, req, load, _config) {
        //store config
        config = config || _config;

        if (!siteRoot) {
            siteRoot = path.resolve(config.dir || path.dirname(config.out.toString()), config.siteRoot || '.') + '/';
            if (isWindows)
                siteRoot = siteRoot.replace(/\\/g, '/');
        }

        //external URLS don't get added (just like JS requires)
        if (name.match(absUrlRegEx))
            return load();

        var fileUrl = req.toUrl(name + '.css');
        if (isWindows)
            fileUrl = fileUrl.replace(/\\/g, '/');

        // rebase to the output directory if based on the source directory;
        // baseUrl points always to the output directory, fileUrl only if
        // it is not prefixed by a computed path (relative too)
        var fileSiteUrl = fileUrl;
        if (fileSiteUrl.indexOf(baseUrl) < 0) {
            var appRoot = req.toUrl(config.appDir);
            if (isWindows)
                appRoot = appRoot.replace(/\\/g, '/');
            if (fileSiteUrl.indexOf(appRoot) == 0)
                fileSiteUrl = siteRoot + fileSiteUrl.substring(appRoot.length);
        }

        //add to the buffer
        cssBuffer[name] = normalize(loadFile(fileUrl), fileSiteUrl, siteRoot);

        load();
    }

    cssAPI.normalize = function (name, normalize) {
        if (name.substr(name.length - 4, 4) == '.css')
            name = name.substr(0, name.length - 4);
        return normalize(name);
    }

    cssAPI.write = function (pluginName, moduleName, write, parse) {
        var cssModule;

        //external URLS don't get added (just like JS requires)
        if (moduleName.match(absUrlRegEx))
            return;

        layerBuffer.push(cssBuffer[moduleName]);

        if (!global._requirejsCssData) {
            global._requirejsCssData = {
                usedBy: { css: true },
                css: ''
            }
        } else {
            global._requirejsCssData.usedBy.css = true;
        }

        if (config.buildCSS != false) {
            var style = cssBuffer[moduleName];

            if (config.writeCSSModule && style) {
                if (writeCSSForLayer) {
                    writeCSSForLayer = false;
                    write(writeCSSDefinition);
                }

                cssModule = 'define(["@writecss"], function(writeCss){\n writeCss("' + escape(compress(style)) + '");\n})';
            }
            else {
                cssModule = 'define(function(){})';
            }

            write.asModule(pluginName + '!' + moduleName, cssModule);
        }
    }

    cssAPI.onLayerEnd = function (write, data) {
        if (config.separateCSS && config.IESelectorLimit)
            throw 'RequireCSS: separateCSS option is not compatible with ensuring the IE selector limit';

        if (config.separateCSS) {
            var outPath = data.path.replace(/(\.js)?$/, '.css');
            console.log('Writing CSS! file: ' + outPath + '\n');

            var css = layerBuffer.join('');

            process.nextTick(function () {
                if (global._requirejsCssData) {
                    css = global._requirejsCssData.css = css + global._requirejsCssData.css;
                    delete global._requirejsCssData.usedBy.css;
                    if (Object.keys(global._requirejsCssData.usedBy).length === 0) {
                        delete global._requirejsCssData;
                    }
                }

                saveFile(outPath, compress(css));
            });

        }
        else if (config.buildCSS != false && config.writeCSSModule != true) {
            var styles = config.IESelectorLimit ? layerBuffer : [layerBuffer.join('')];
            for (var i = 0; i < styles.length; i++) {
                if (styles[i] == '')
                    return;
                write(
                    "(function(c){var d=document,a='appendChild',i='styleSheet',s=d.createElement('style');s.type='text/css';d.getElementsByTagName('head')[0][a](s);s[i]?s[i].cssText=c:s[a](d.createTextNode(c));})\n"
                    + "('" + escape(compress(styles[i])) + "');\n"
                );
            }
        }
        //clear layer buffer for next layer
        layerBuffer = [];
        writeCSSForLayer = true;
    }

    return cssAPI;
});
